/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { RealWebSocket } from './RealWebSocket';
import { Dns, DnsSystem } from './Dns';
import { CookieJar, NO_COOKIES } from './CookieJar'
import { Factory as WebSocketFactory, WebSocket } from './WebSocket';
import { Authenticator, AuthenticatorNONE } from './Authenticator';
import { EventListener, Factory as EventListenerFactory, EventListenerNONE } from './EventListener';
import { ConnectionPool } from './ConnectionPool';
import { ProxySelector } from './ProxySelector';
import { SocketFactory } from './Socket/SocketFactory';
import { X509TrustManager, X509TrustManagerSystem } from './Socket/X509TrustManager';
import { SSLSocketFactory } from './Socket/SSLSocketFactory';
import { Protocol } from './Protocol';
import { ConnectionSpec } from './ConnectionSpec';
import { HostnameVerifier } from './HostnameVerifier';
import { CertificatePinner, DEFAULT as CertificatePinnerDEFAULT} from './CertificatePinner';
import { checkDuration, kotlinCheck, kotlinCheckNotNull, kotlinRequire } from './utils/Utils';
import { TimeUnit } from './utils/TimeUnit';
import { Call, Factory as CallFactory } from './Call'
import { List } from '@kit.ArkTS';
import { Proxy } from './Socket/Proxy';
import { NullProxySelector } from './proxy/NullProxySelector';
import { CertificateChainCleaner } from './tls/CertificateChainCleaner';
import { RouteDatabase } from './connection/RouteDatabase';
import { RealCall } from './connection/RealCall';
import OkHostnameVerifier from './tls/OkHostnameVerifier'
import { Cache } from './Cache';
import { Dispatcher } from './Dispatcher'
import { Interceptor } from './Interceptor';
import { WebSocketListener } from './WebSocketListener';
import { Request } from './Request'
import { IllegalStateException } from './Error/error';


const DEFAULT_PROTOCOLS = [Protocol.HTTP_1_1] // Protocol.HTTP_2  http2暂不支持
// 后续会从左到右迭代,优先匹配更安全的tls,其次http
const DEFAULT_CONNECTION_SPECS = [ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT]

export class OkHttpClient implements WebSocketFactory, CallFactory{
    private _dispatcher: Dispatcher
    get dispatcher(): Dispatcher {
        return this._dispatcher
    }

    private _connectionPool: ConnectionPool
    get connectionPool(): ConnectionPool {
        return this._connectionPool
    }
    /**
     * Returns an immutable list of interceptors that observe the full span of each call: from before
     * the connection is established (if any) until after the response source is selected (either the
     * origin server, cache, or both).
     */
    // @get:JvmName("interceptors") val interceptors: List<Interceptor> =
    // builder.interceptors.toImmutableList()
    private _interceptors: List<Interceptor>
    get interceptors(): List<Interceptor> {
      return this._interceptors
    }

    /**
     * Returns an immutable list of interceptors that observe a single network request and response.
     * These interceptors must call [Interceptor.Chain.proceed] exactly once: it is an error for
     * a network interceptor to short-circuit or repeat a network request.
     */
    // @get:JvmName("networkInterceptors") val networkInterceptors: List<Interceptor> =
    // builder.networkInterceptors.toImmutableList()
    private _networkInterceptors: List<Interceptor>
    get networkInterceptors(): List<Interceptor> {
        return this._networkInterceptors
    }

    private _eventListenerFactory: EventListenerFactory
    get eventListenerFactory(): EventListenerFactory {
        return this._eventListenerFactory
    }

    private _retryOnConnectionFailure: boolean
    get retryOnConnectionFailure(): boolean {
        return this._retryOnConnectionFailure
    }

    private _authenticator: Authenticator
    get authenticator(): Authenticator {
        return this._authenticator
    }

    private _followRedirects: boolean
    get followRedirects(): boolean {
        return this._followRedirects
    }

    private _followSslRedirects: boolean
    get followSslRedirects(): boolean {
        return this._followSslRedirects
    }

    private _cookieJar: CookieJar
    get cookieJar(): CookieJar {
        return this._cookieJar
    }

    // @get:JvmName("cache") val cache: Cache? = builder.cache
    private _cache: Cache | null = null
    get cache(): Cache {
        return this._cache
    }

    private _dns: Dns
    get dns(): Dns {
        return this._dns
    }

    private _proxy?: Proxy
    get proxy(): Proxy {
        return this._proxy
    }

    private _proxySelector: ProxySelector
    get proxySelector(): ProxySelector {
        return this._proxySelector
    }

    private _proxyAuthenticator: Authenticator
    get proxyAuthenticator(): Authenticator {
        return this._proxyAuthenticator
    }

    private _socketFactory: SocketFactory
    get socketFactory(): SocketFactory {
        return this._socketFactory
    }

    private sslSocketFactoryOrNull?: SSLSocketFactory
    get sslSocketFactory(): SSLSocketFactory {
        if (this.sslSocketFactoryOrNull === null){
            throw new IllegalStateException("CLEARTEXT-only client")
        }
        return this.sslSocketFactoryOrNull
    }

    private _x509TrustManager?: X509TrustManager
    get x509TrustManager(): X509TrustManager{
        return this._x509TrustManager
    }

    private _connectionSpecs: ConnectionSpec[]
    get connectionSpecs(): ConnectionSpec[]{
        return this._connectionSpecs
    }

    private _protocols: Protocol[]
    get protocols(): Protocol[]{
        return this._protocols
    }

    private _hostnameVerifier: HostnameVerifier
    get hostnameVerifier(): HostnameVerifier{
        return this._hostnameVerifier
    }

    private _certificatePinner: CertificatePinner
    get certificatePinner(): CertificatePinner{
        return this._certificatePinner
    }

    private _certificateChainCleaner?: CertificateChainCleaner
    get certificateChainCleaner(): CertificateChainCleaner{
        return this._certificateChainCleaner
    }

    /**
     * Default call timeout (in milliseconds). By default there is no timeout for complete calls, but
     * there is for the connect, write, and read actions within a call.
     */
    private _callTimeoutMillis: number
    get callTimeoutMillis(): number {
        return this._callTimeoutMillis
    }

    // /** Default connect timeout (in milliseconds). The default is 10 seconds. */
    private _connectTimeoutMillis: number
    get connectTimeoutMillis(): number {
        return this._connectTimeoutMillis
    }

    // /** Default read timeout (in milliseconds). The default is 10 seconds. */
    private _readTimeoutMillis: number
    get readTimeoutMillis(): number {
        return this._readTimeoutMillis
    }

    // /** Default write timeout (in milliseconds). The default is 10 seconds. */
    private _writeTimeoutMillis: number
    get writeTimeoutMillis(): number {
        return this._writeTimeoutMillis
    }

    // /** Web socket and HTTP/2 ping interval (in milliseconds). By default pings are not sent. */
    private _pingIntervalMillis: number
    get pingIntervalMillis(): number {
        return this._pingIntervalMillis
    }

    // /**
    //  * Minimum outbound web socket message size (in bytes) that will be compressed.
    //  * The default is 1024 bytes.
    //  */
    private _minWebSocketMessageToCompress: number
    get minWebSocketMessageToCompress(): number {
        return this._minWebSocketMessageToCompress
    }

    routeDatabase: RouteDatabase

    private constructor(dispatcher: Dispatcher,
        connectionPool: ConnectionPool,
        interceptors: List<Interceptor>,
        networkInterceptors: List<Interceptor>,
        eventListenerFactory: EventListenerFactory,
        retryOnConnectionFailure: boolean,
        authenticator: Authenticator,
        followRedirects: boolean,
        followSslRedirects: boolean,
        cookieJar: CookieJar,
        cache: Cache | null,
        dns: Dns,
        proxy: Proxy,
        proxySelector: ProxySelector,
        proxyAuthenticator: Authenticator,
        socketFactory: SocketFactory,
        sslSocketFactoryOrNull: SSLSocketFactory,
        connectionSpecs: ConnectionSpec[],
        protocols: Protocol[],
        hostnameVerifier: HostnameVerifier,
        certificatePinner: CertificatePinner,
        certificateChainCleaner: CertificateChainCleaner,
        callTimeoutMillis: number,
        connectTimeoutMillis: number,
        readTimeoutMillis: number,
        writeTimeoutMillis: number,
        pingIntervalMillis: number,
        minWebSocketMessageToCompress: number,
        routeDatabase: RouteDatabase,
        x509TrustManagerOrNull: X509TrustManager,
    ) {
        this._dispatcher = dispatcher
        this._connectionPool = connectionPool
        this._interceptors = interceptors
        this._networkInterceptors = networkInterceptors
        this._eventListenerFactory = eventListenerFactory
        this._retryOnConnectionFailure = retryOnConnectionFailure
        this._authenticator = authenticator
        this._followRedirects = followRedirects
        this._followSslRedirects = followSslRedirects
        this._cookieJar = cookieJar
        this._cache = cache
        this._dns = dns
        this._proxy = proxy
        if (proxy !== null){
            this._proxySelector = NullProxySelector;
        }else{
            this._proxySelector = proxySelector ?? ProxySelector.getDefault() ?? NullProxySelector;
        }
        this._proxyAuthenticator = proxyAuthenticator
        this._socketFactory = socketFactory
        this._connectionSpecs = connectionSpecs
        this._protocols = protocols
        this._hostnameVerifier = hostnameVerifier
        this._callTimeoutMillis = callTimeoutMillis
        this._connectTimeoutMillis = connectTimeoutMillis
        this._readTimeoutMillis = readTimeoutMillis
        this._writeTimeoutMillis = writeTimeoutMillis
        this._pingIntervalMillis = pingIntervalMillis
        this._minWebSocketMessageToCompress = minWebSocketMessageToCompress
        this.routeDatabase = routeDatabase ?? new RouteDatabase()
        this.init(sslSocketFactoryOrNull, certificateChainCleaner, x509TrustManagerOrNull, certificatePinner)
    }

    init(sslSocketFactoryOrNull: SSLSocketFactory,certificateChainCleaner: CertificateChainCleaner,x509TrustManagerOrNull: X509TrustManager,certificatePinner: CertificatePinner){
        if (this._connectionSpecs.every(spec => !spec.isTls)){
            this.sslSocketFactoryOrNull = null
            this._certificateChainCleaner = null
            this._x509TrustManager = null
            this._certificatePinner = CertificatePinnerDEFAULT
        }else if (sslSocketFactoryOrNull !== null && sslSocketFactoryOrNull !== undefined){
            this.sslSocketFactoryOrNull = sslSocketFactoryOrNull
            this._certificateChainCleaner = certificateChainCleaner
            this._x509TrustManager = x509TrustManagerOrNull
            this._certificatePinner = certificatePinner.withCertificateChainCleaner(certificateChainCleaner)
        }else {
            // 获取系统ca证书
            this._x509TrustManager = X509TrustManagerSystem.SYSTEM//Platform.get().platformTrustManager()
            this.sslSocketFactoryOrNull = new SSLSocketFactory(undefined,undefined,undefined,undefined,this._x509TrustManager)//Platform.get().newSslSocketFactory(this._x509TrustManager)
            this._certificateChainCleaner = undefined//CertificateChainCleaner.get(this._x509TrustManager)
            this._certificatePinner = certificatePinner.withCertificateChainCleaner(certificateChainCleaner)
        }
        this.verifyClientState()
    }

    verifyClientState(){
        this._interceptors.forEach(interceptor => {
            kotlinCheck(interceptor !== null, 'Null interceptor in interceptors list');
        })
        this._networkInterceptors.forEach(interceptor => {
            kotlinCheck(interceptor !== null, 'Null interceptor in networkInterceptors list');
        })
        if (this.connectionSpecs.every(spec => !spec.isTls)) {
            kotlinCheck(this.sslSocketFactoryOrNull === null, ``)
            // kotlinCheck(this.certificateChainCleaner === null, ``)
            kotlinCheck(this.x509TrustManager === null, ``)
            kotlinCheck(this.certificatePinner === CertificatePinnerDEFAULT, ``)
        }else {
            kotlinCheckNotNull(this.sslSocketFactoryOrNull, 'sslSocketFactoryOrNull == null')
            // kotlinCheckNotNull(this.certificateChainCleaner, "certificateChainCleaner == null")
            kotlinCheckNotNull(this.x509TrustManager, "x509TrustManager == null")
        }
    }

    /** Prepares the [request] to be executed at some point in the future. */
    newCall(request: Request): Call{
        return new RealCall(this, request, false);
    }

    /** Uses [request] to connect a new web socket. */
    newWebSocket(request: Request, listener: WebSocketListener): WebSocket{
        // if (request == undefined || request == null) {
        //     throw new Error('Incorrect request parameters');
        // }
        // var newCall = new HttpCall(this, request, true)
        // request.client = this
        // let ws = new RealWebSocket(request, listener)
        // newCall.webSocket(ws)
        // newCall.execute()
        return null;

        // let webSocket = RealWebSocket(
        //     taskRunner = TaskRunner.INSTANCE,
        //     originalRequest = request,
        //     listener = listener,
        //     random = Random(),
        //     pingIntervalMillis = pingIntervalMillis.toLong(),
        //     extensions = null, // Always null for clients.
        //     minimumDeflateSize = minWebSocketMessageToCompress
        // )
        // webSocket.connect(this)
        // return webSocket
    }

    newBuilder() {
        return new OkHttpClient.Builder(this)
    }

    static Builder = class Builder{
         #dispatcher: Dispatcher = new Dispatcher()
         #connectionPool: ConnectionPool = new ConnectionPool()
         #interceptors: List<Interceptor> = new List<Interceptor>()
         #networkInterceptors: List<Interceptor> = new List<Interceptor>()
         #eventListenerFactory: EventListenerFactory = EventListenerNONE.asFactory()
         #retryOnConnectionFailure = true
         #authenticator: Authenticator = AuthenticatorNONE
         #followRedirects = true
         #followSslRedirects = true
         #cookieJar: CookieJar = NO_COOKIES
         #cache: Cache | null = null  // 待审视
         #dns: Dns = DnsSystem.SYSTEM
         #proxy: Proxy | null = null
         #proxySelector: ProxySelector | null = null  // 待审视
         #proxyAuthenticator: Authenticator = AuthenticatorNONE
         #socketFactory: SocketFactory = SocketFactory.getDefault()  // 待审视
         #sslSocketFactoryOrNull: SSLSocketFactory | null = null  // 待审视
         #x509TrustManagerOrNull: X509TrustManager | null = null  // 待审视
         #connectionSpecs: ConnectionSpec[] = DEFAULT_CONNECTION_SPECS  // 待审视
         #protocols: Protocol[] = DEFAULT_PROTOCOLS
         #hostnameVerifier: HostnameVerifier = OkHostnameVerifier.INSTANCE  // 待审视
         #certificatePinner: CertificatePinner = CertificatePinnerDEFAULT  // 待审视
         #certificateChainCleaner: CertificateChainCleaner | null = null  // 待审视
         #callTimeout = 0
         #connectTimeout = 10_000
         #readTimeout = 10_000
         #writeTimeout = 10_000
         #pingInterval = 0
         #minWebSocketMessageToCompress = RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE
         #routeDatabase: RouteDatabase | null = null  // 待审视

        constructor(okHttpClient?: OkHttpClient) {
             if (okHttpClient === null || okHttpClient === undefined) {
                return
            }
            this.#dispatcher = okHttpClient.dispatcher
            this.#connectionPool = okHttpClient.connectionPool
            for (const interceptor of okHttpClient.interceptors){
                this.#interceptors.add(interceptor)
            }
            for (const networkInterceptor of okHttpClient.networkInterceptors){
                this.#interceptors.add(networkInterceptor)
            }
            this.#eventListenerFactory = okHttpClient.eventListenerFactory
            this.#retryOnConnectionFailure = okHttpClient.retryOnConnectionFailure
            this.#authenticator = okHttpClient.authenticator
            this.#followRedirects = okHttpClient.followRedirects
            this.#followSslRedirects = okHttpClient.followSslRedirects
            this.#cookieJar = okHttpClient.cookieJar
            this.#cache = okHttpClient.cache
            this.#dns = okHttpClient.dns
            this.#proxy = okHttpClient.proxy
            this.#proxySelector = okHttpClient.proxySelector
            this.#proxyAuthenticator = okHttpClient.proxyAuthenticator
            this.#socketFactory = okHttpClient.socketFactory
            this.#sslSocketFactoryOrNull = okHttpClient.sslSocketFactoryOrNull
            this.#x509TrustManagerOrNull = okHttpClient.x509TrustManager
            this.#connectionSpecs = okHttpClient.connectionSpecs
            this.#protocols = okHttpClient.protocols
            this.#hostnameVerifier = okHttpClient.hostnameVerifier
            this.#certificatePinner = okHttpClient.certificatePinner
            this.#certificateChainCleaner = okHttpClient.certificateChainCleaner
            this.#callTimeout = okHttpClient.callTimeoutMillis
            this.#connectTimeout = okHttpClient.connectTimeoutMillis
            this.#readTimeout = okHttpClient.readTimeoutMillis
            this.#writeTimeout = okHttpClient.writeTimeoutMillis
            this.#pingInterval = okHttpClient.pingIntervalMillis
            this.#minWebSocketMessageToCompress = okHttpClient.minWebSocketMessageToCompress
            this.#routeDatabase = okHttpClient.routeDatabase
        }

        /**
         * Sets the dispatcher used to set policy and execute asynchronous requests. Must not be null.
         */
        dispatcher(dispatcher: Dispatcher){
            this.#dispatcher = dispatcher
            return this
        }

        /**
         * Sets the connection pool used to recycle HTTP and HTTPS connections.
         *
         * If unset, a new connection pool will be used.
         */
        connectionPool(connectionPool: ConnectionPool){
            this.#connectionPool = connectionPool
            return this
        }
        /**
         * Returns a modifiable list of interceptors that observe the full span of each call: from
         * before the connection is established (if any) until after the response source is selected
         * (either the origin server, cache, or both).
         */
        get interceptors(){
            return this.#interceptors
        }

        addInterceptor(interceptor: Interceptor){
            this.#interceptors.add(interceptor);
            return this;
        }

        // lamda表达式暂不实现
        // @JvmName("-addInterceptor") // Prefix with '-' to prevent ambiguous overloads from Java.
        // inline fun addInterceptor(crossinline block: (chain: Interceptor.Chain) -> Response) =
        // addInterceptor(Interceptor { chain -> block(chain) })

        /**
         * Returns a modifiable list of interceptors that observe a single network request and response.
         * These interceptors must call [Interceptor.Chain.proceed] exactly once: it is an error for a
         * network interceptor to short-circuit or repeat a network request.
         */
        networkInterceptors(): List<Interceptor> {
            return this.#networkInterceptors
        }

        addNetworkInterceptor(interceptor: Interceptor):Builder {
            this.#networkInterceptors.add(interceptor)
            return this;
        }

        // lamda表达式暂不实现
        // @JvmName("-addNetworkInterceptor") // Prefix with '-' to prevent ambiguous overloads from Java.
        // inline fun addNetworkInterceptor(crossinline block: (chain: Interceptor.Chain) -> Response) =
        // addNetworkInterceptor(Interceptor { chain -> block(chain) })
        // addInterceptor(interceptor: Interceptor) {
        //     this.interceptors.push(interceptor);
        //     return this;
        // }

        /**
         * Configure a single client scoped listener that will receive all analytic events for this
         * client.
         *
         * @see EventListener for semantics and restrictions on listener implementations.
         */
        eventListener(eventListener: EventListener){
            this.#eventListenerFactory = eventListener.asFactory()
            return this;
        }

        /**
         * Configure a factory to provide per-call scoped listeners that will receive analytic events
         * for this client.
         *
         * @see EventListener for semantics and restrictions on listener implementations.
         */
        eventListenerFactory(eventListenerFactory: EventListenerFactory){
            this.#eventListenerFactory = eventListenerFactory
            return this;
        }

        /**
         * Configure this client to retry or not when a connectivity problem is encountered. By default,
         * this client silently recovers from the following problems:
         *
         * * **Unreachable IP addresses.** If the URL's host has multiple IP addresses,
         *   failure to reach any individual IP address doesn't fail the overall request. This can
         *   increase availability of multi-homed services.
         *
         * * **Stale pooled connections.** The [ConnectionPool] reuses sockets
         *   to decrease request latency, but these connections will occasionally time out.
         *
         * * **Unreachable proxy servers.** A [ProxySelector] can be used to
         *   attempt multiple proxy servers in sequence, eventually falling back to a direct
         *   connection.
         *
         * Set this to false to avoid retrying requests when doing so is destructive. In this case the
         * calling application should do its own recovery of connectivity failures.
         */
        retryOnConnectionFailure(retryOnConnectionFailure: boolean){
            this.#retryOnConnectionFailure = retryOnConnectionFailure
            return this
        }

        /**
         * Sets the authenticator used to respond to challenges from origin servers. Use
         * [proxyAuthenticator] to set the authenticator for proxy servers.
         *
         * If unset, the [no authentication will be attempted][Authenticator.NONE].
         */
        authenticator(authenticator: Authenticator){
            this.#authenticator = authenticator
            return this;
        }

        /** Configure this client to follow redirects. If unset, redirects will be followed. */
        followRedirects(followRedirects: boolean){
            this.#followRedirects = followRedirects
            return this;
        }

        /**
         * Configure this client to allow protocol redirects from HTTPS to HTTP and from HTTP to HTTPS.
         * Redirects are still first restricted by [followRedirects].  Defaults to true.
         *
         * @param followProtocolRedirects whether to follow redirects between HTTPS and HTTP.
         */
        followSslRedirects(followProtocolRedirects: boolean){
            this.#followSslRedirects = followProtocolRedirects
            return this
        }

        /**
         * Sets the handler that can accept cookies from incoming HTTP responses and provides cookies to
         * outgoing HTTP requests.
         *
         * If unset, [no cookies][CookieJar.NO_COOKIES] will be accepted nor provided.
         */
        cookieJar(cookieJar: CookieJar){
            this.#cookieJar = cookieJar
            return this
        }

        /** Sets the response cache to be used to read and write cached responses. */
        cache(cache: Cache): Builder{
            this.#cache = cache
            return this;
        }

        /**
         * Sets the DNS service used to lookup IP addresses for hostnames.
         *
         * If unset, the [system-wide default][Dns.SYSTEM] DNS will be used.
         */
        dns(dns: Dns){
            if (dns !== this.#dns) {
                this.#routeDatabase = null
            }
            this.#dns = dns
            return this;
        }

        /**
         * Sets the HTTP proxy that will be used by connections created by this client. This takes
         * precedence over [proxySelector], which is only honored when this proxy is null (which it is
         * by default). To disable proxy use completely, call `proxy(Proxy.NO_PROXY)`.
         */
        proxy(proxy: Proxy){
            if (proxy != this.#proxy) {
                this.#routeDatabase = null
            }
            this.#proxy = proxy
            return this
        }

        /**
         * Sets the proxy selection policy to be used if no [proxy][proxy] is specified explicitly. The
         * proxy selector may return multiple proxies; in that case they will be tried in sequence until
         * a successful connection is established.
         *
         * If unset, the [system-wide default][ProxySelector.getDefault] proxy selector will be used.
         */
        proxySelector(proxySelector: ProxySelector){
            if (proxySelector != this.#proxySelector) {
                // this.routeDatabase = null
            }

            this.#proxySelector = proxySelector
            return this;
        }

        /**
         * Sets the authenticator used to respond to challenges from proxy servers. Use [authenticator]
         * to set the authenticator for origin servers.
         *
         * If unset, the [no authentication will be attempted][Authenticator.NONE].
         */
        proxyAuthenticator(proxyAuthenticator: Authenticator){
            if (proxyAuthenticator != this.#proxyAuthenticator) {
                this.#routeDatabase = null
            }

            this.#proxyAuthenticator = proxyAuthenticator
            return this;
        }

        /**
         * Sets the socket factory used to create connections. OkHttp only uses the parameterless
         * [SocketFactory.createSocket] method to create unconnected sockets. Overriding this method,
         * e. g., allows the socket to be bound to a specific local address.
         *
         * If unset, the [system-wide default][SocketFactory.getDefault] socket factory will be used.
         */
        socketFactory(socketFactory: SocketFactory){

            kotlinRequire(!(socketFactory instanceof  SSLSocketFactory), "socketFactory instanceof SSLSocketFactory")

            if (socketFactory != this.#socketFactory) {
                this.#routeDatabase = null
            }

            this.#socketFactory = socketFactory
            return this;
        }

        /**
         * Sets the socket factory and trust manager used to secure HTTPS connections. If unset, the
         * system defaults will be used.
         *
         * Most applications should not call this method, and instead use the system defaults. Those
         * classes include special optimizations that can be lost if the implementations are decorated.
         *
         * If necessary, you can create and configure the defaults yourself with the following code:
         *
         * ```
         * TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
         * TrustManagerFactory.getDefaultAlgorithm());
         * trustManagerFactory.init((KeyStore) null);
         * TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
         * if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
         *     throw new IllegalStateException("Unexpected default trust managers:"
         *         + Arrays.toString(trustManagers));
         * }
         * X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
         *
         * SSLContext sslContext = SSLContext.getInstance("TLS");
         * sslContext.init(null, new TrustManager[] { trustManager }, null);
         * SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
         *
         * OkHttpClient client = new OkHttpClient.Builder()
         *     .sslSocketFactory(sslSocketFactory, trustManager)
         *     .build();
         * ```
         *
         * ## TrustManagers on Android are Weird!
         *
         * Trust managers targeting Android must also define a method that has this signature:
         *
         * ```
         *    @SuppressWarnings("unused")
         *    public List<X509Certificate> checkServerTrusted(
         *        X509Certificate[] chain, String authType, String host) throws CertificateException {
         *    }
         * ```
         *
         * This method works like [X509TrustManager.checkServerTrusted] but it receives the hostname of
         * the server as an extra parameter. Regardless of what checks this method performs, OkHttp will
         * always check that the server's certificates match its hostname using the [HostnameVerifier].
         * See [android.net.http.X509TrustManagerExtensions] for more information.
         */
        sslSocketFactory(sslSocketFactory: SSLSocketFactory, trustManager: X509TrustManager){
            if (sslSocketFactory !== this.#sslSocketFactoryOrNull || trustManager !== this.#x509TrustManagerOrNull) {
                this.#routeDatabase = null
            }

            this.#sslSocketFactoryOrNull = sslSocketFactory
            // this.certificateChainCleaner = CertificateChainCleaner.get(trustManager)
            this.#x509TrustManagerOrNull = trustManager
            return this;
        }

        connectionSpecs(connectionSpecs: ConnectionSpec[]){
            if (connectionSpecs != this.#connectionSpecs) {
                this.#routeDatabase = null
            }

            // this._connectionSpecs = connectionSpecs.toImmutableList()
            return this;
        }

        /**
         * Configure the protocols used by this client to communicate with remote servers. By default
         * this client will prefer the most efficient transport available, falling back to more
         * ubiquitous protocols. Applications should only call this method to avoid specific
         * compatibility problems, such as web servers that behave incorrectly when HTTP/2 is enabled.
         *
         * The following protocols are currently supported:
         *
         * * [http/1.1][rfc_2616]
         * * [h2][rfc_7540]
         * * [h2 with prior knowledge(cleartext only)][rfc_7540_34]
         *
         * **This is an evolving set.** Future releases include support for transitional
         * protocols. The http/1.1 transport will never be dropped.
         *
         * If multiple protocols are specified, [ALPN][alpn] will be used to negotiate a transport.
         * Protocol negotiation is only attempted for HTTPS URLs.
         *
         * [Protocol.HTTP_1_0] is not supported in this set. Requests are initiated with `HTTP/1.1`. If
         * the server responds with `HTTP/1.0`, that will be exposed by [Response.protocol].
         *
         * [alpn]: http://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg
         * [rfc_2616]: http://www.w3.org/Protocols/rfc2616/rfc2616.html
         * [rfc_7540]: https://tools.ietf.org/html/rfc7540
         * [rfc_7540_34]: https://tools.ietf.org/html/rfc7540#section-3.4
         *
         * @param protocols the protocols to use, in order of preference. If the list contains
         *     [Protocol.H2_PRIOR_KNOWLEDGE] then that must be the only protocol and HTTPS URLs will not
         *     be supported. Otherwise the list must contain [Protocol.HTTP_1_1]. The list must
         *     not contain null or [Protocol.HTTP_1_0].
         */
        protocols(protocols: Protocol[]){
            // Create a private copy of the list.
            let protocolsCopy = protocols
            // Validate that the list has everything we require and nothing we forbid.
            kotlinRequire(protocolsCopy.includes(Protocol.H2_PRIOR_KNOWLEDGE) || protocolsCopy.includes(Protocol.HTTP_1_1),
            `protocols must contain h2_prior_knowledge or http/1.1: ${protocolsCopy}`)
            kotlinRequire(!protocolsCopy.includes(Protocol.H2_PRIOR_KNOWLEDGE) || protocolsCopy.length <= 1,
            `protocols containing h2_prior_knowledge cannot use other protocols: ${protocolsCopy}`)
            kotlinRequire(!protocolsCopy.includes(Protocol.HTTP_1_0),
            `protocols must not contain http/1.0: ${protocolsCopy}`)
            kotlinRequire(!protocolsCopy.includes(null),
            "protocols must not contain null")

            // Remove protocols that we no longer support.
            // @Suppress("DEPRECATION")
            const spdyIndex = protocolsCopy.indexOf(Protocol.SPDY_3)
            if (spdyIndex !== -1) {
                protocolsCopy.splice(spdyIndex, 1)
            }

            if (protocolsCopy !== this.#protocols) {
                this.#routeDatabase = null
            }

            // Assign as an unmodifiable list. This is effectively immutable.
            this.#protocols = protocolsCopy
            return this;
        }

        /**
         * Sets the verifier used to confirm that response certificates apply to requested hostnames for
         * HTTPS connections.
         *
         * If unset, a default hostname verifier will be used.
         */
        hostnameVerifier(hostnameVerifier: HostnameVerifier){
            if (hostnameVerifier != this.#hostnameVerifier) {
                this.#routeDatabase = null
            }
            this.#hostnameVerifier = hostnameVerifier
            return this;
        }

        /**
         * Sets the certificate pinner that constrains which certificates are trusted. By default HTTPS
         * connections rely on only the [SSL socket factory][sslSocketFactory] to establish trust.
         * Pinning certificates avoids the need to trust certificate authorities.
         */
        certificatePinner(certificatePinner: CertificatePinner){
            if (certificatePinner != this.#certificatePinner) {
                this.#routeDatabase = null
            }

            this.#certificatePinner = certificatePinner
            return this;
        }

        /**
         * Sets the default timeout for complete calls. A value of 0 means no timeout, otherwise values
         * must be between 1 and [Integer.MAX_VALUE] when converted to milliseconds.
         *
         * The call timeout spans the entire call: resolving DNS, connecting, writing the request body,
         * server processing, and reading the response body. If the call requires redirects or retries
         * all must complete within one timeout period.
         *
         * The default value is 0 which imposes no timeout.
         */
        callTimeout(timeout: number, unit: TimeUnit){
            this.#callTimeout = checkDuration("timeout", timeout, unit)
            return this;
        }

        // @IgnoreJRERequirement
        // fun callTimeout(duration: Duration) = apply {
        //     callTimeout(duration.toMillis(), MILLISECONDS)
        // }

        /**
         * Sets the default connect timeout for new connections. A value of 0 means no timeout,
         * otherwise values must be between 1 and [Integer.MAX_VALUE] when converted to milliseconds.
         *
         * The connect timeout is applied when connecting a TCP socket to the target host. The default
         * value is 10 seconds.
         */
        connectTimeout(timeout: number, unit: TimeUnit){
            this.#connectTimeout = checkDuration("timeout", timeout, unit)
            return this;
        }

        /**
         * Sets the default connect timeout for new connections. A value of 0 means no timeout,
         * otherwise values must be between 1 and [Integer.MAX_VALUE] when converted to milliseconds.
         *
         * The connect timeout is applied when connecting a TCP socket to the target host. The default
         * value is 10 seconds.
         */
        // @IgnoreJRERequirement
        // fun connectTimeout(duration: Duration) = apply {
        //     connectTimeout(duration.toMillis(), MILLISECONDS)
        // }

        /**
         * Sets the default read timeout for new connections. A value of 0 means no timeout, otherwise
         * values must be between 1 and [Integer.MAX_VALUE] when converted to milliseconds.
         *
         * The read timeout is applied to both the TCP socket and for individual read IO operations
         * including on [Source] of the [Response]. The default value is 10 seconds.
         *
         * @see Socket.setSoTimeout
         * @see Source.timeout
         */
        readTimeout(timeout: number, unit: TimeUnit){
            this.#readTimeout = checkDuration("timeout", timeout, unit)
            return this;
        }

        /**
         * Sets the default read timeout for new connections. A value of 0 means no timeout, otherwise
         * values must be between 1 and [Integer.MAX_VALUE] when converted to milliseconds.
         *
         * The read timeout is applied to both the TCP socket and for individual read IO operations
         * including on [Source] of the [Response]. The default value is 10 seconds.
         *
         * @see Socket.setSoTimeout
         * @see Source.timeout
         */
        // @IgnoreJRERequirement
        // fun readTimeout(duration: Duration) = apply {
        //     readTimeout(duration.toMillis(), MILLISECONDS)
        // }

        /**
         * Sets the default write timeout for new connections. A value of 0 means no timeout, otherwise
         * values must be between 1 and [Integer.MAX_VALUE] when converted to milliseconds.
         *
         * The write timeout is applied for individual write IO operations. The default value is 10
         * seconds.
         *
         * @see Sink.timeout
         */
        writeTimeout(timeout: number, unit: TimeUnit){
            this.#writeTimeout = checkDuration("timeout", timeout, unit)
            return this;
        }

        /**
         * Sets the default write timeout for new connections. A value of 0 means no timeout, otherwise
         * values must be between 1 and [Integer.MAX_VALUE] when converted to milliseconds.
         *
         * The write timeout is applied for individual write IO operations. The default value is 10
         * seconds.
         *
         * @see Sink.timeout
         */
        // @IgnoreJRERequirement
        // fun writeTimeout(duration: Duration) = apply {
        //     writeTimeout(duration.toMillis(), MILLISECONDS)
        // }

        /**
         * Sets the interval between HTTP/2 and web socket pings initiated by this client. Use this to
         * automatically send ping frames until either the connection fails or it is closed. This keeps
         * the connection alive and may detect connectivity failures.
         *
         * If the server does not respond to each ping with a pong within `interval`, this client will
         * assume that connectivity has been lost. When this happens on a web socket the connection is
         * canceled and its listener is [notified][WebSocketListener.onFailure]. When it happens on an
         * HTTP/2 connection the connection is closed and any calls it is carrying
         * [will fail with an IOException][java.io.IOException].
         *
         * The default value of 0 disables client-initiated pings.
         */
        pingInterval(interval: number, unit: TimeUnit){
            this.#pingInterval = checkDuration("interval", interval, unit)
            return this;
        }

        /**
         * Sets the interval between HTTP/2 and web socket pings initiated by this client. Use this to
         * automatically send ping frames until either the connection fails or it is closed. This keeps
         * the connection alive and may detect connectivity failures.
         *
         * If the server does not respond to each ping with a pong within `interval`, this client will
         * assume that connectivity has been lost. When this happens on a web socket the connection is
         * canceled and its listener is [notified][WebSocketListener.onFailure]. When it happens on an
         * HTTP/2 connection the connection is closed and any calls it is carrying
         * [will fail with an IOException][java.io.IOException].
         *
         * The default value of 0 disables client-initiated pings.
         */
        // @IgnoreJRERequirement
        // fun pingInterval(duration: Duration) = apply {
        //     pingInterval(duration.toMillis(), MILLISECONDS)
        // }

        /**
         * Sets minimum outbound web socket message size (in bytes) that will be compressed.
         *
         * Set to 0 to enable compression for all outbound messages.
         *
         * 1024 by default.
         */
        minWebSocketMessageToCompress(bytes: number){
            kotlinRequire(bytes >= 0,
                `minWebSocketMessageToCompress must be positive: ${bytes}`
            )
            this.#minWebSocketMessageToCompress = bytes
            return this;
        }

        build(): OkHttpClient {
            return new OkHttpClient(this.#dispatcher,
                this.#connectionPool,
                this.#interceptors,
                this.#networkInterceptors,
                this.#eventListenerFactory,
                this.#retryOnConnectionFailure,
                this.#authenticator,
                this.#followRedirects,
                this.#followSslRedirects,
                this.#cookieJar,
                this.#cache,
                this.#dns,
                this.#proxy,
                this.#proxySelector,
                this.#proxyAuthenticator,
                this.#socketFactory,
                this.#sslSocketFactoryOrNull,
                this.#connectionSpecs,
                this.#protocols,
                this.#hostnameVerifier,
                this.#certificatePinner,
                this.#certificateChainCleaner,
                this.#callTimeout,
                this.#connectTimeout,
                this.#readTimeout,
                this.#writeTimeout,
                this.#pingInterval,
                this.#minWebSocketMessageToCompress,
                this.#routeDatabase,
                this.#x509TrustManagerOrNull
            )
        }
    }
}